import { AD_UNIT_ID_RESULT, DEFAULT_ALLOW_DECIMAL } from '../../config'
import {
  cloneTokens,
  cycleState,
  formatNumberToken,
  markPriceTokens,
  NumberToken,
  parseTicketText,
  serializeTokens,
  Token,
  TokenState,
  TextToken,
  tokensToLines,
} from '../../utils/ticket'

interface ViewTextSegment {
  type: 'text'
  id: string
  raw: string
}

interface ViewNumberSegment {
  type: 'number'
  id: string
  display: string
  state: TokenState
}

type ViewSegment = ViewTextSegment | ViewNumberSegment

interface ViewLine {
  id: string
  selected: boolean
  segments: ViewSegment[]
}

type ModifyTarget = 'yellow' | 'red'

interface PageData extends WechatMiniprogram.IAnyObject {
  lines: ViewLine[]
  mode: 'plus' | 'minus'
  delta: string
  allowDecimal: boolean
  modifyTarget: ModifyTarget
  modifyTargetLabel: string
  lineSelections: Record<string, boolean>
  hasSelection: boolean
  adUnitId: string
  adError: boolean
  showDeltaPicker: boolean
  presetDeltas: number[]
  showCustomInput: boolean
  customDeltaInput: string
}

interface LoadPayload {
  rawText: string
  allowDecimal?: boolean
}

interface InternalState {
  rawText: string
  tokens: Token[]
  baselineTokens: Token[]
  lineGroups: Array<Array<TextToken | NumberToken>>
  lineRects: LineRect[]
  dragSelecting: boolean
  dragMode: 'select' | 'deselect' | null
  lastDragLineId: string | null
}

interface LineRect {
  id: string
  top: number
  bottom: number
}

type PageInstance = WechatMiniprogram.Page.Instance<PageData, Record<string, unknown>>

function buildViewLines(
  lineGroups: Array<Array<TextToken | NumberToken>>,
  selections: Record<string, boolean>,
): ViewLine[] {
  return lineGroups.map((line, index) => {
    const id = `line-${index}`
    return {
      id,
      selected: !!selections[id],
      segments: line.map<ViewSegment>((segment) => {
        if (segment.type === 'text') {
          return {
            type: 'text',
            id: segment.id,
            raw: segment.raw,
          }
        }
        return {
          type: 'number',
          id: segment.id,
          display: formatNumberToken(segment as NumberToken),
          state: (segment as NumberToken).state,
        }
      }),
    }
  })
}

function getDecimalPlaces(value: string): number {
  const trimmed = value.trim()
  if (!trimmed.includes('.')) {
    return 0
  }
  const [, decimals = ''] = trimmed.split('.')
  return decimals.length
}

function applyDeltaToTokens(
  tokens: Token[],
  delta: number,
  mode: 'plus' | 'minus',
  target: ModifyTarget,
  deltaPrecision: number,
  allowDecimal: boolean,
): number {
  const direction = mode === 'plus' ? 1 : -1
  let affected = 0

  tokens.forEach((token) => {
    if (token.type !== 'number') {
      return
    }

    if (target === 'yellow' && token.state !== 'yellow') {
      return
    }
    if (target === 'red' && token.state !== 'red') {
      return
    }

    const precision = allowDecimal ? Math.max(token.precision, deltaPrecision) : 0
    const factor = Math.pow(10, precision)
    const currentScaled = Math.round(token.value * factor)
    const deltaScaled = Math.round(delta * factor)
    const signedScaled = currentScaled + direction * deltaScaled
    token.value = allowDecimal ? signedScaled / factor : Math.round(signedScaled)
    token.precision = precision
    affected += 1
  })

  return affected
}

const pageState: InternalState = {
  rawText: '',
  tokens: [],
  baselineTokens: [],
  lineGroups: [],
  lineRects: [],
  dragSelecting: false,
  dragMode: null,
  lastDragLineId: null,
}

function updateModifyTarget(page: PageInstance, target: ModifyTarget) {
  const label = target === 'yellow' ? '仅修改黄色（点此切换）' : '仅修改红色（点此切换）'
  page.setData({ modifyTarget: target, modifyTargetLabel: label })
}

function updateLineRects(page: PageInstance) {
  wx.nextTick(() => {
    page
      .createSelectorQuery()
      .selectAll('.line-row')
      .boundingClientRect((rects) => {
        if (!Array.isArray(rects)) {
          pageState.lineRects = []
          return
        }
        const typedRects = rects as WechatMiniprogram.BoundingClientRectCallbackResult[]
        pageState.lineRects = typedRects.map((rect, index) => ({
          id: `line-${index}`,
          top: rect.top,
          bottom: rect.bottom,
        }))
      })
      .exec()
  })
}

function refreshLines(page: PageInstance, preserveSelections = true) {
  const grouped = tokensToLines(pageState.tokens)
  pageState.lineGroups = grouped
  const previousSelections = preserveSelections ? page.data.lineSelections || {} : {}
  const selections: Record<string, boolean> = {}
  grouped.forEach((_, index) => {
    const id = `line-${index}`
    selections[id] = !!previousSelections[id]
  })
  const hasSelection = Object.values(selections).some(Boolean)
  page.setData({
    lines: buildViewLines(grouped, selections),
    lineSelections: selections,
    hasSelection,
  })
  updateLineRects(page)
}

function setLineSelection(page: PageInstance, lineId: string, selected: boolean) {
  if (!lineId || !pageState.lineGroups.length) {
    return
  }
  const currentSelections = { ...(page.data.lineSelections || {}) }
  if (currentSelections[lineId] === selected) {
    return
  }
  currentSelections[lineId] = selected
  const hasSelection = Object.values(currentSelections).some(Boolean)
  page.setData({
    lineSelections: currentSelections,
    hasSelection,
    lines: buildViewLines(pageState.lineGroups, currentSelections),
  })
}

function serializeSelectedLineGroups(
  lineGroups: Array<Array<TextToken | NumberToken>>,
  selectedIds: string[],
): string {
  if (!selectedIds.length) {
    return ''
  }
  const lines = selectedIds
    .map((id) => Number(id.replace('line-', '')))
    .filter((index) => !Number.isNaN(index))
    .map((index) => lineGroups[index] || [])
    .map((line) =>
      line
        .map((segment) =>
          segment.type === 'number'
            ? formatNumberToken(segment as NumberToken)
            : (segment as TextToken).raw,
        )
        .join(''),
    )
  return lines.join('\n')
}

function findLineIdByClientY(clientY: number): string | null {
  const hit = pageState.lineRects.find((rect) => clientY >= rect.top && clientY <= rect.bottom)
  return hit ? hit.id : null
}

function reparseTokens(page: PageInstance, allowDecimal: boolean) {
  const tokens = parseTicketText(pageState.rawText, { allowDecimal })
  markPriceTokens(tokens)
  pageState.tokens = tokens
  pageState.baselineTokens = cloneTokens(tokens)
  page.setData({ allowDecimal })
  updateModifyTarget(page, 'yellow')
  refreshLines(page, false)
}

function initialize(page: PageInstance, payload: LoadPayload) {
  pageState.rawText = payload.rawText || ''
  const allowDecimal =
    typeof payload.allowDecimal === 'boolean' ? payload.allowDecimal : DEFAULT_ALLOW_DECIMAL
  reparseTokens(page, allowDecimal)
  page.setData({ allowDecimal })
}

Page<PageData, Record<string, unknown>>({
  data: {
    lines: [],
    mode: 'plus',
    delta: '50',
    allowDecimal: DEFAULT_ALLOW_DECIMAL,
    modifyTarget: 'yellow',
    modifyTargetLabel: '仅修改黄色（点此切换）',
    lineSelections: {},
    hasSelection: false,
    adUnitId: AD_UNIT_ID_RESULT,
    adError: false,
    showDeltaPicker: false,
    presetDeltas: [50, 100, 200, 300, 400, 500, 1000],
    showCustomInput: false,
    customDeltaInput: '',
  },

  onLoad(this: PageInstance) {
    const eventChannel = (this as any).getOpenerEventChannel
      ? (this as any).getOpenerEventChannel()
      : undefined
    if (eventChannel) {
      eventChannel.on('loadData', (payload: LoadPayload) => {
        initialize(this, payload)
      })
    }
  },

  onCheckboxTouchStart(this: PageInstance, event: WechatMiniprogram.TouchEvent) {
    const lineId = event.currentTarget.dataset.id as string
    if (!lineId) {
      return
    }
    const currentSelections = this.data.lineSelections || {}
    const isSelected = !!currentSelections[lineId]
    pageState.dragSelecting = true
    pageState.dragMode = isSelected ? 'deselect' : 'select'
    setLineSelection(this, lineId, pageState.dragMode === 'select')
    pageState.lastDragLineId = lineId
  },

  onCheckboxTouchMove(this: PageInstance, event: WechatMiniprogram.TouchEvent) {
    if (!pageState.dragSelecting) {
      return
    }
    const touch = event.touches && event.touches[0]
    if (!touch) {
      return
    }
    const lineId = findLineIdByClientY(touch.clientY)
    if (!lineId || lineId === pageState.lastDragLineId) {
      return
    }
    setLineSelection(this, lineId, pageState.dragMode === 'select')
    pageState.lastDragLineId = lineId
  },

  onCheckboxTouchEnd(this: PageInstance) {
    pageState.dragSelecting = false
    pageState.dragMode = null
    pageState.lastDragLineId = null
    updateLineRects(this)
  },

  onToggleState(this: PageInstance, event: WechatMiniprogram.TouchEvent) {
    const id = event.currentTarget.dataset.id as string
    if (!id) {
      return
    }
    const target = pageState.tokens.find((token) => token.type === 'number' && token.id === id) as
      | NumberToken
      | undefined
    if (!target) {
      return
    }
    target.state = cycleState(target.state)
    refreshLines(this)
  },

  onToggleMode() {
    this.setData({ mode: this.data.mode === 'plus' ? 'minus' : 'plus' })
  },

  onDeltaInput(e: WechatMiniprogram.Input) {
    this.setData({ delta: e.detail.value })
  },

  onOpenDeltaPicker() {
    this.setData({ showDeltaPicker: true, showCustomInput: false, customDeltaInput: '' })
  },
  onCloseDeltaPicker() {
    this.setData({ showDeltaPicker: false, showCustomInput: false, customDeltaInput: '' })
  },
  onPickPreset(this: PageInstance, e: WechatMiniprogram.TouchEvent) {
    const value = String(e.currentTarget.dataset.value || '')
    if (!value) return
    this.setData({ delta: value, showDeltaPicker: false, showCustomInput: false, customDeltaInput: '' })
  },
  onShowCustomInput() {
    this.setData({ showCustomInput: true, customDeltaInput: '' })
  },
  onCustomDeltaInput(e: WechatMiniprogram.Input) {
    this.setData({ customDeltaInput: e.detail.value })
  },
  onConfirmCustomDelta() {
    const v = (this.data.customDeltaInput || '').trim()
    if (!v) { wx.showToast({ title: '请输入数值', icon: 'none' }); return }
    if (Number.isNaN(Number(v))) { wx.showToast({ title: '格式不正确', icon: 'none' }); return }
    this.setData({ delta: v, showDeltaPicker: false, showCustomInput: false, customDeltaInput: '' })
  },

  onApply(this: PageInstance) {
    const deltaStr = this.data.delta.trim()
    if (!deltaStr) {
      wx.showToast({ title: '请输入调整数值', icon: 'none' })
      return
    }

    const deltaValue = Number(deltaStr)
    if (Number.isNaN(deltaValue)) {
      wx.showToast({ title: '调整数值格式不正确', icon: 'none' })
      return
    }

    if (!this.data.allowDecimal && !Number.isInteger(deltaValue)) {
      wx.showToast({ title: '当前模式不支持小数', icon: 'none' })
      return
    }

    const deltaPrecision = this.data.allowDecimal ? getDecimalPlaces(deltaStr) : 0
    const affected = applyDeltaToTokens(
      pageState.tokens,
      deltaValue,
      this.data.mode,
      'yellow',
      deltaPrecision,
      this.data.allowDecimal,
    )

    if (affected === 0) {
      wx.showToast({ title: '未找到可调整的数字', icon: 'none' })
      return
    }

    refreshLines(this)
  },

  onApplyToRed(this: PageInstance) {
    ;(this as any).applyForTarget('red')
  },
  onApplyToYellow(this: PageInstance) {
    ;(this as any).applyForTarget('yellow')
  },

  applyForTarget(this: PageInstance, target: ModifyTarget) {
    const deltaStr = this.data.delta.trim()
    if (!deltaStr) {
      wx.showToast({ title: '请选择或输入数值', icon: 'none' })
      return
    }
    const deltaValue = Number(deltaStr)
    if (Number.isNaN(deltaValue)) {
      wx.showToast({ title: '调整数值格式不正确', icon: 'none' })
      return
    }
    const deltaPrecision = this.data.allowDecimal ? getDecimalPlaces(deltaStr) : 0
    const affected = applyDeltaToTokens(
      pageState.tokens,
      deltaValue,
      this.data.mode,
      target,
      deltaPrecision,
      this.data.allowDecimal,
    )
    if (affected === 0) {
      wx.showToast({ title: '未找到可调整的数字', icon: 'none' })
      return
    }
    refreshLines(this)
  },

  onClearHighlights(this: PageInstance) {
    pageState.tokens.forEach((token) => {
      if (token.type === 'number') {
        token.state = 'none'
      }
    })
    updateModifyTarget(this, 'yellow')
    refreshLines(this)
  },

  onDeleteSelectedLines(this: PageInstance) {
    const selections = this.data.lineSelections || {}
    const selectedIds = Object.keys(selections).filter((id) => selections[id])
    if (!selectedIds.length) {
      wx.showToast({ title: '请先勾选要删除的行', icon: 'none' })
      return
    }
    const selectedIndexes = new Set(
      selectedIds
        .map((id) => Number(id.replace('line-', '')))
        .filter((n) => !Number.isNaN(n)),
    )

    // 依据行索引过滤 Token（按 linebreak 分行）
    const newTokens: Token[] = []
    let currentLine = 0
    pageState.tokens.forEach((t) => {
      if (!selectedIndexes.has(currentLine)) {
        if (t.type !== 'linebreak') {
          newTokens.push({ ...t })
        } else {
          // 仅在未删除且当前行不是最后可见行时保留换行
          newTokens.push({ ...t })
        }
      }
      if (t.type === 'linebreak') {
        currentLine += 1
      }
    })

    // 规范化：如果末尾有多余换行，可保留一次；刷新视图会重新分行
    pageState.tokens = newTokens
    // 清空选择
    this.setData({ lineSelections: {}, hasSelection: false })
    refreshLines(this, false)
  },

  onCycleModifyTarget(this: PageInstance) {
    const next: ModifyTarget = this.data.modifyTarget === 'yellow' ? 'red' : 'yellow'
    updateModifyTarget(this, next)
  },

  onCopyResult() {
    if (!pageState.tokens.length) {
      wx.showToast({ title: '无可复制内容', icon: 'none' })
      return
    }
    const selections = this.data.lineSelections || {}
    const selectedIds = Object.keys(selections).filter((id) => selections[id])
    const text = selectedIds.length
      ? serializeSelectedLineGroups(pageState.lineGroups, selectedIds)
      : serializeTokens(pageState.tokens)
    if (!text) {
      wx.showToast({ title: '选中行暂无内容', icon: 'none' })
      return
    }
    wx.setClipboardData({ data: text })
  },

  onClearSelections(this: PageInstance) {
    const selections: Record<string, boolean> = {}
    if (pageState.lineGroups.length) {
      pageState.lineGroups.forEach((_, index) => {
        selections[`line-${index}`] = false
      })
    }
    this.setData({
      lineSelections: selections,
      hasSelection: false,
      lines: buildViewLines(pageState.lineGroups, selections),
    })
  },

  onReset(this: PageInstance) {
    pageState.tokens = cloneTokens(pageState.baselineTokens)
    updateModifyTarget(this, 'yellow')
    refreshLines(this)
  },

  onAdError() {
    this.setData({ adError: true })
  },

  // 右上角菜单：转发到聊天
  onShareAppMessage() {
    // 为了保证打开后能看到完整功能，统一分享回首页
    return {
      title: '数字计算·票务文本处理',
      path: '/pages/home/home',
    }
  },
  // 右上角菜单：分享到朋友圈
  onShareTimeline() {
    return {
      title: '数字计算·票务文本处理',
    }
  },
})
